use std::{collections::HashSet, fmt};

#[derive(Debug, Clone, PartialEq)]
pub enum Expr {
    Csti(usize),
    Cstf(f32),
    Var(String),
    Add(Option<Box<Expr>>, Option<Box<Expr>>),
    Sub(Option<Box<Expr>>, Option<Box<Expr>>),
    Mul(Option<Box<Expr>>, Option<Box<Expr>>),
    Div(Option<Box<Expr>>, Option<Box<Expr>>),
    Pow(Option<Box<Expr>>, Option<Box<Expr>>),
    Log(Option<Box<Expr>>, Option<Box<Expr>>),
    Sin(Option<Box<Expr>>),
    Cos(Option<Box<Expr>>),
    Tan(Option<Box<Expr>>),
    ArcSin(Option<Box<Expr>>),
    ArcCos(Option<Box<Expr>>),
    ArcTan(Option<Box<Expr>>),
    Sec(Option<Box<Expr>>),
    Csc(Option<Box<Expr>>),
    Cot(Option<Box<Expr>>),
    Neg(Option<Box<Expr>>),
    Funcall(String, Option<Box<Expr>>), // name, list
    Bra,
    Ket,
    Comma,
}

impl fmt::Display for Expr {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match &self {
            Expr::Csti(x) => write!(f, "{}", x),
            Expr::Cstf(x) => write!(f, "{}", x),
            Expr::Var(x) => write!(f, "{}", x),
            Expr::Funcall(name, paras) => write!(
                f,
                "{}({})",
                name.to_owned(),
                if let Some(y) = paras {
                    y.to_owned()
                } else {
                    Box::new(Expr::Var("nil".to_string()))
                }
            ),
            Expr::Add(x, y) => write!(
                f,
                "[{}+{}]",
                if let Some(x) = x {
                    x.to_owned()
                } else {
                    Box::new(Expr::Var("nil".to_string()))
                },
                if let Some(y) = y {
                    y.to_owned()
                } else {
                    Box::new(Expr::Var("nil".to_string()))
                }
            ),
            Expr::Sub(x, y) => write!(
                f,
                "[{}-{}]",
                if let Some(x) = x {
                    x.to_owned()
                } else {
                    Box::new(Expr::Var("nil".to_string()))
                },
                if let Some(y) = y {
                    y.to_owned()
                } else {
                    Box::new(Expr::Var("nil".to_string()))
                }
            ),
            Expr::Mul(x, y) => write!(
                f,
                "[{}*{}]",
                if let Some(x) = x {
                    x.to_owned()
                } else {
                    Box::new(Expr::Var("nil".to_string()))
                },
                if let Some(y) = y {
                    y.to_owned()
                } else {
                    Box::new(Expr::Var("nil".to_string()))
                }
            ),
            Expr::Div(x, y) => write!(
                f,
                "[{}/{}]",
                if let Some(x) = x {
                    x.to_owned()
                } else {
                    Box::new(Expr::Var("nil".to_string()))
                },
                if let Some(y) = y {
                    y.to_owned()
                } else {
                    Box::new(Expr::Var("nil".to_string()))
                }
            ),
            Expr::Pow(x, y) => write!(
                f,
                "[{}^{}]",
                if let Some(x) = x {
                    x.to_owned()
                } else {
                    Box::new(Expr::Var("nil".to_string()))
                },
                if let Some(y) = y {
                    y.to_owned()
                } else {
                    Box::new(Expr::Var("nil".to_string()))
                }
            ),
            Expr::Log(x, y) => write!(
                f,
                "[log{}({})]",
                if let Some(x) = x {
                    x.to_owned()
                } else {
                    Box::new(Expr::Var("nil".to_string()))
                },
                if let Some(y) = y {
                    y.to_owned()
                } else {
                    Box::new(Expr::Var("nil".to_string()))
                }
            ),
            Expr::ArcSin(x) => write!(
                f,
                "arc_sin({})",
                if let Some(x) = x {
                    x.to_owned()
                } else {
                    Box::new(Expr::Var("nil".to_string()))
                }
            ),
            Expr::ArcCos(x) => write!(
                f,
                "arc_cos({})",
                if let Some(x) = x {
                    x.to_owned()
                } else {
                    Box::new(Expr::Var("nil".to_string()))
                }
            ),
            Expr::ArcTan(x) => write!(
                f,
                "arc_tan({})",
                if let Some(x) = x {
                    x.to_owned()
                } else {
                    Box::new(Expr::Var("nil".to_string()))
                }
            ),
            Expr::Sin(x) => write!(
                f,
                "sin({})",
                if let Some(x) = x {
                    x.to_owned()
                } else {
                    Box::new(Expr::Var("nil".to_string()))
                }
            ),
            Expr::Cos(x) => write!(
                f,
                "cos({})",
                if let Some(x) = x {
                    x.to_owned()
                } else {
                    Box::new(Expr::Var("nil".to_string()))
                }
            ),
            Expr::Tan(x) => write!(
                f,
                "tan({})",
                if let Some(x) = x {
                    x.to_owned()
                } else {
                    Box::new(Expr::Var("nil".to_string()))
                }
            ),
            Expr::Neg(x) => write!(
                f,
                "-{}",
                if let Some(x) = x {
                    x.to_owned()
                } else {
                    Box::new(Expr::Var("nil".to_string()))
                }
            ),
            Expr::Sec(x) => write!(
                f,
                "sec({})",
                if let Some(x) = x {
                    x.to_owned()
                } else {
                    Box::new(Expr::Var("nil".to_string()))
                }
            ),
            Expr::Csc(x) => write!(
                f,
                "csc({})",
                if let Some(x) = x {
                    x.to_owned()
                } else {
                    Box::new(Expr::Var("nil".to_string()))
                }
            ),
            Expr::Cot(x) => write!(
                f,
                "sin({})",
                if let Some(x) = x {
                    x.to_owned()
                } else {
                    Box::new(Expr::Var("nil".to_string()))
                }
            ),
            Expr::Bra => write!(f, "("),
            Expr::Ket => write!(f, ")"),
            Expr::Comma => write!(f, ","),
        }
    }
}
impl Expr {
    // wrap in Some(Box::new(Expr));
    pub fn wrap(self) -> Option<Box<Expr>> {
        Some(Box::new(self))
    }
    pub fn count_vals(&self, set: &mut HashSet<String>) -> bool {
        match self.to_owned() {
            Self::Var(s) => set.insert(s),
            Self::Add(a, b)
            | Self::Sub(a, b)
            | Self::Mul(a, b)
            | Self::Div(a, b)
            | Self::Pow(a, b)
            | Self::Log(a, b) => {
                a.unwrap().count_vals(set);
                b.unwrap().count_vals(set)
            }
            Self::Neg(t)
            | Self::Sin(t)
            | Self::Cos(t)
            | Self::Tan(t)
            | Self::Sec(t)
            | Self::Csc(t)
            | Self::Cot(t)
            | Self::ArcSin(t)
            | Self::ArcCos(t)
            | Self::ArcTan(t) => t.unwrap().count_vals(set),
            _ => false,
        }
    }
}
